unit md3s;

{*
Quake 3 Model Loader (MD3 Loader)
Author Jan Horn (jhorn@global.co.za) http://home.global.co.za/~jhorn
Modified from (v1.0/7 October 2001) by dvh for Game Mundo, improved interpolation and model linking
}

interface

uses
  Classes, SysUtils, gl, glu, glut, textures, debugs, VUMaths;

type
  TMyGlMatrix = array[0..15] of glFloat;

  TMD3Header = record
    ID : array[1..4] of char;               // id = IDP3
    Version       : integer;                // Version = 15
    Filename      : array[1..68] of char;
    numBoneFrames : integer;
    numTags       : integer;
    numMeshes     : integer;
    numMaxSkins   : integer;
    headerLength  : integer;
    TagStart      : integer;
    TagEnd        : integer;
    FileSize      : integer;
  end;

  TBoneFrame = record
    mins : array[0..2] of glFloat;
    maxs : array[0..2] of glFloat;
    Position : array[0..2] of glFloat;
    Scale    : glFloat;
    Creator  : array[1..16] of char;
  end;

  TAnim = record
    Name           : string;
    FirstFrame     : integer;
    numFrames      : integer;
    LoopingFrames  : integer;
    FPS            : integer;
  end;

  TRotationMatrix = array[0..2, 0..2] of glFloat;
  TVector = array[0..2] of glFloat;
  TTag = record
    Name     : array[1..64] of char;
    Position : TVector;
    Rotation : TRotationMatrix;
  end;
  
  TTriangle = record
    Vertex : array[0..2] of integer;
  end;

  TTexCoord = record
    Coord : array[0..1] of glFloat;
  end;

  TVertex = record
    Vertex : array[0..2] of Smallint;
    Normal : array[0..1] of Byte;
  end;

  TMeshHeader = record
    ID   : array[1..4] of char;
    Name : array[1..68] of char;
    numMeshFrames : integer;
    numSkins     : integer;
    numVertexes  : integer;
    numTriangles : integer;
    triStart     : integer;
    headerSize   : integer;
    TexVectorStart : integer;
    VertexStart  : integer;
    MeshSize     : integer;
  end;

  TMesh = record
    MeshHeader : TMeshHeader;
    Skins      : array of array[1..68] of char;
    Triangle   : array of TTriangle;
    TexCoord   : array of TTexCoord;
    Vertex     : array of TVertex;
    Texture    : TTexture;
    SetTexture : boolean;
  end;

  // md3 model static data (can be shared within more players)
  TMD3ModelStatic = class
  protected
    procedure LoadAnim(AFileName : string);
  public
    Md3Name    : string;
    Header     : TMD3header;
    BoneFrames : array of TBoneFrame;
    Tags       : array of TTag;
    Meshes     : array of TMesh;
    Anim       : array of TAnim;
    procedure LoadModel(AFileName : string);
    procedure LoadSkin(AImagePath, AFileName : string);
    procedure DrawModelInt(const currentFrame, nexFrame : integer; const pol: real);
    function FindTag(AName : string) : TTag;
  end;
  
  // md3 model dynamic data
  TMD3ModelDynamic = class
  protected
  public
    ModelStatic : TMD3ModelStatic;
    BaseTag     : TTag;         // this tag will be handled as base (translated to [0,0,0] with default rotation)
    BaseMatrix  : TMyGlMatrix;  // this is transformation which brings baseTag (and the rest of model) to [0,0,0], that's the whole point of using base tag for linking
    BaseAngle  : glFloat;        // extra rotation for base joint
    Frame       : integer;      // Current frame to Draw
    StartFrame  : integer;
    EndFrame    : integer;
    NextFrame   : integer;      // Next frame to Draw
    FPS         : integer;
    Poll        : glFloat;      // Interpolation Time;
    LastUpdate  : glFloat;      // last Draw
    Links      : array of TMD3ModelDynamic;
    Animation : string;
    procedure UpdateFrame(time : glFLoat);
    procedure SetAnimation(AAnimationName : string);
    procedure DrawModel;
    procedure DrawLinked(AModel : TMd3ModelDynamic; ATime : glFloat);
    procedure LinkModel(tagname : string; var MD3Model : TMD3ModelDynamic; ABaseTag : string);
    procedure Base(ABaseTag : string);
  end;
  
  // shared md3 models (static of course)
  TMd3ModelStaticList = class(TList)
  public
    destructor Destroy; override;
    function Load(APath, AModel, ASkin : string) : TMd3ModelStatic;
  end;  
  
var Md3ModelStaticList : TMd3ModelStaticList;

function CharArrToStr(const C : array of Char) : string;
procedure CalculateBaseMatrixFromTag(var AMatrix : TMyGlMatrix; ATag : TTag);
function TagToStr(ATag : TTag) : string;
function TagToMatrix4(ATag : TTag) : TMatrix4;

implementation

function TagToMatrix4(ATag : TTag) : TMatrix4;
{*
Convert tag to Matrix4
}
begin
  with ATag do
    result := Matrix4([Rotation[0,0], Rotation[0,1], Rotation[0,2], 0,
                       Rotation[1,0], Rotation[1,1], Rotation[1,2], 0,
                       Rotation[2,0], Rotation[2,1], Rotation[2,2], 0,
                       Position[0],   Position[1],   Position[2],   1]);
end;

const 
  BOTH_DEATH1 = 0;
  BOTH_DEAD1  = 1;
  BOTH_DEATH2 = 2;
  BOTH_DEAD2  = 3;
  BOTH_DEATH3 = 4;
  BOTH_DEAD3  = 5;
  TORSO_GESTURE = 6;
  TORSO_ATTACK  = 7;
  TORSO_ATTACK2 = 8;
  TORSO_DROP    = 9;
  TORSO_RAISE   = 10;
  TORSO_STAND   = 11;
  TORSO_STAND2  = 12;
  LEGS_WALKCR   = 13;
  LEGS_WALK     = 14;
  LEGS_RUN      = 15;
  LEGS_BACK     = 16;
  LEGS_SWIM     = 17;
  LEGS_JUMP     = 18;
  LEGS_LAND     = 19;
  LEGS_JUMPB    = 20;
  LEGS_LANDB    = 21;
  LEGS_IDLE     = 22;
  LEGS_IDLECR   = 23;
  LEGS_TURN     = 24;
  MAX_ANIMATIONS = 25;

var anorms : array[0..255, 0..255, 0..2] of real;

function TagToStr(ATag : TTag) : string;
{*
Return tag position and rotation as string for debug purposes
}
begin
  result := 
    CharArrToStr(ATag.Name)+
    ' pos=('+format('%1.2f,%1.2f,%1.2f',[ATag.Position[0],ATag.Position[1],ATag.Position[2]])+')'+
    ' rot=('+
      format('%1.2f,%1.2f,%1.2f',[ATag.Rotation[0,0],ATag.Rotation[0,1],ATag.Rotation[0,2]])+' / '+
      format('%1.2f,%1.2f,%1.2f',[ATag.Rotation[1,0],ATag.Rotation[1,1],ATag.Rotation[1,2]])+' / '+
      format('%1.2f,%1.2f,%1.2f',[ATag.Rotation[2,0],ATag.Rotation[2,1],ATag.Rotation[2,2]])+')';
end;

function ArcTan2(Y, X: Extended): Extended;
{*
Arc tan squared function
}
asm
  FLD     Y
  FLD     X
  FPATAN
  FWAIT
end;

function ArcCos(X: Extended): Extended;
{*
Arc cos function
}
begin
  Result := ArcTan2(Sqrt(1 - X*X), X);
end;

function CharArrToStr(const C : array of char) : string;
{*
Convert the array of characters to a string
}
var I : integer;
begin
  I :=0;
  result :='';
  while C[i] <> #0 do
  begin
    result := result + C[I];
    Inc(I);
  end;
end;

procedure InitNormals;
{*
Create a lookup table of normals. Faster this way
}
var I, J : integer;
    alpha, beta : real;
begin
  for I :=0 to 255 do
  begin
    for J :=0 to 255 do
    begin
      alpha :=2*I*pi/255;
      beta :=2*j*pi/255;
      anorms[i][j][0] := cos(beta) * sin(alpha);
      anorms[i][j][1] := sin(beta) * sin(alpha);
      anorms[i][j][2] := cos(alpha);
    end;
  end;
end;

// TMD3Model

function TMD3ModelStatic.FindTag(AName : string) : TTag;
{*
Find tag by name, raise exception if tag with this name does not exist
}
var i : integer;
begin
  Debug.Msg(dlBegin,'TMD3ModelStatic.FindTag("'+AName+'") in '+inttostr(Header.numTags)+' tags');
  for i := 0 to Header.numTags-1 do
  begin
    Debug.Msg(dlDetail,'  comparing: "'+trim(Tags[i].Name)+'"');
    if trim(Tags[i].Name) = trim(AName) then
    begin
      Debug.Msg(dlDetail,'    found');
      result := Tags[i];
      exit;
    end;
  end;  
  raise Exception.Create('Model "'+Md3Name+'" has not tag "'+AName+'"');      
end;

procedure TMD3ModelStatic.DrawModelInt(const currentFrame, nexFrame : integer; const pol: real);
{*
Draw the Model using interpolation
}
var i, j, k : integer;
    TriangleNum, currentMesh, currentOffsetVertex,
    currentVertex, nextCurrentOffsetVertex : integer;
    normU, normV : integer;
    s, t : glFloat;
    v, n : array[0..2] of glFloat;
    nextV, nextN : array[0..2] of glFloat;
begin
  //writeln(md3name,' ... cf=',CurrentFrame,' nf=',NexFrame,' =',Pol:1:8);
  glFrontFace(GL_CCW);
  for k :=0 to header.numMeshes-1 do
  begin
    currentMesh :=k;
    currentOffsetVertex :=currentFrame * meshes[currentMesh].MeshHeader.numVertexes;
    // interpolation
    nextCurrentOffsetVertex := nexFrame * meshes[currentMesh].MeshHeader.numVertexes;
    TriangleNum := Meshes[currentMesh].MeshHeader.numTriangles;
    if meshes[k].SetTexture then
    begin
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, meshes[k].Texture.Id)
    end;
    for I :=0 to TriangleNum-1 do
    begin
      glBegin(GL_TRIANGLES);
      for J := 2 downto 0 do
      begin
        currentVertex := Meshes[currentMesh].Triangle[i].vertex[j];

        v[0] :=meshes[currentMesh].vertex[currentOffsetVertex + currentVertex].Vertex[0] / 64;
        v[1] :=meshes[currentMesh].vertex[currentOffsetVertex + currentVertex].Vertex[1] / 64;
        v[2] :=meshes[currentMesh].vertex[currentOffsetVertex + currentVertex].Vertex[2] / 64;

        nextv[0] :=meshes[currentMesh].vertex[nextCurrentOffsetVertex + currentVertex].Vertex[0] / 64;
        nextv[1] :=meshes[currentMesh].vertex[nextCurrentOffsetVertex + currentVertex].Vertex[1] / 64;
        nextv[2] :=meshes[currentMesh].vertex[nextCurrentOffsetVertex + currentVertex].Vertex[2] / 64;

        normU := meshes[currentMesh].vertex[currentOffsetVertex + currentVertex].Normal[0];
        normV := meshes[currentMesh].vertex[currentOffsetVertex + currentVertex].Normal[1];

        n[0] :=aNorms[normU, normV, 0];
        n[1] :=aNorms[normU, normV, 1];
        n[2] :=aNorms[normU, normV, 2];

        // interpolated U, V and N
        normU := meshes[currentMesh].vertex[nextCurrentOffsetVertex + currentVertex].Normal[0];
        normV := meshes[currentMesh].vertex[nextCurrentOffsetVertex + currentVertex].Normal[1];

        nextN[0] := anorms[normU, normV, 0];
        nextN[1] := anorms[normU, normV, 1];
        nextN[2] := anorms[normU, normV, 2];

        s :=meshes[currentMesh].TexCoord[currentVertex].Coord[0];
        t :=meshes[currentMesh].TexCoord[currentVertex].Coord[1];

        glTexCoord2f(s, {1-}t);

        // interpolation
        glNormal3f(n[0] + pol * (nextN[0] - n[0]), n[1] + pol * (nextN[1] - n[1]), n[2] + pol * (nextN[2] - n[2]));
        glVertex3f(v[0] + pol * (nextV[0] - v[0]), v[1] + pol * (nextV[1] - v[1]), v[2] + pol * (nextV[2] - v[2]));
      end;
      glEnd;
    end;
  end;
end;

procedure CalculateBaseMatrixFromTag(var AMatrix : TMyGlMatrix; ATag : TTag);
{*
Calculate base matrix from tag (BaseMatrix brings tag back to origin)
}
var m4 : TMatrix4;
    m : integer;
begin
  // check the tag
  if trim(ATag.Name) = '' then
    raise Exception.Create('CalculateBaseMatrixFromTag - undefined tag name (parent model not linked?)');
  // tag
  AMatrix[0] := ATag.Rotation[0, 0];
  AMatrix[1] := ATag.Rotation[0, 1];
  AMatrix[2] := ATag.Rotation[0, 2];
  AMatrix[3] := 0;
  AMatrix[4] := ATag.Rotation[1, 0];
  AMatrix[5] := ATag.Rotation[1, 1];
  AMatrix[6] := ATag.Rotation[1, 2];
  AMatrix[7] := 0;
  AMatrix[8] := ATag.Rotation[2, 0];
  AMatrix[9] := ATag.Rotation[2, 1];
  AMatrix[10]:= ATag.Rotation[2, 2];
  AMatrix[11]:= 0;
  AMatrix[12]:= ATag.Position[0];
  AMatrix[13]:= ATag.Position[1];
  AMatrix[14]:= ATag.Position[2];
  AMatrix[15]:= 1;
  // inverse matrix bring tag back to origin
  Matrix4(AMatrix,m4);
  m4 := Mat4Inverse(m4);  
  for m := 0 to 15 do
    AMatrix[m] := m4.Singles[m];
end;

procedure TMD3ModelDynamic.LinkModel(tagname: string; var MD3Model: TMD3ModelDynamic; ABaseTag : string);
{*
Links a Model to a tag. (head is linked to torso)
}
var I,tc : integer;
begin
  // init links
  SetLength(Links, ModelStatic.Header.numTags);
  //writeln('  ModelStatic(',ModelStatic.Md3Name,').Header.numTags = ',ModelStatic.Header.numTags);
  tc := 0;
  for I :=0 to ModelStatic.Header.numTags-1 do
  begin
    if CharArrToStr(ModelStatic.tags[i].Name) = tagname then
    begin
      //writeln('  ',ModelStatic.md3name,' is by tagname="',tagname,'" linked[',i,'] to model=',MD3Model.ModelStatic.Md3name);
      Links[i] :={@}MD3Model;
      inc(tc);
      //exit;
      break;
    end;
  end;
  if tc <> 1 then
    raise Exception.Create('LinkModel: Tag "'+tagname+'" not found in model "'+ModelStatic.MD3Name+'"');
  // no linking against base tag?
  if ABaseTag='' then
    raise Exception.Create('LinkModel: Empty base tag (parent tag='+tagname+')');
  // found base tag in second model
  Md3Model.BaseTag := Md3Model.ModelStatic.FindTag(ABaseTag);
  // calculate inverse transformation for base tag
  CalculateBaseMatrixFromTag(Md3Model.BaseMatrix,Md3Model.BaseTag);
end;

procedure TMD3ModelStatic.LoadModel(AFileName: string);
{*
Loads a Model from a .MD3 files, throw exception on error
}
var 
  F : File;
  I : integer;
  MeshOffset : integer;
  s : string;
begin
  Debug.Msg(dlBegin,ClassName+'.LoadModel('+AFileName+')');
  if FileExists(AFileName) = FALSE then
    raise Exception.Create('error: md3 model found "'+AFileName+'"');

  AssignFile(F, AFileName);
  Reset(F,1);

  // copy name
  //writeln('setting md3name to ',AFileName);
  MD3Name := AFileName;

  // read header
  BlockRead(F, Header, Sizeof(Header));
  if (Uppercase(Header.ID) <> 'IDP3') OR (Header.Version <> 15) then
  begin
    CloseFile(F);
    exit;
  end;

  // read boneframes
  SetLength(BoneFrames, Header.numBoneFrames);
  BlockRead(F, BoneFrames[0], Header.numBoneFrames*SizeOf(TBoneFrame));

  // read tags
  SetLength(Tags, Header.numBoneFrames * Header.numTags);
  BlockRead(F, Tags[0], Header.numBoneFrames*Header.numTags*SizeOf(TTag));

  // read meshes
  //writeln('numMeshes=',Header.numMeshes);
  SetLength(Meshes, Header.numMeshes);
  MeshOffset := FilePos(F);

  for I :=0 to Header.numMeshes-1 do
  begin
    Seek(F, MeshOffset);
    BlockRead(F, Meshes[I].MeshHeader, sizeOf(TMeshHeader));

    // Load the Skins
    SetLength(Meshes[I].Skins, Meshes[I].MeshHeader.numSkins);
    BlockRead(F, Meshes[I].Skins[0], 68 * Meshes[I].MeshHeader.numSkins);

    // Triangles
    Seek(F, MeshOffset + Meshes[I].MeshHeader.triStart);
    SetLength(Meshes[I].Triangle, Meshes[I].MeshHeader.numTriangles);
    BlockRead(F, Meshes[I].Triangle[0], sizeOf(TTriangle)*Meshes[I].MeshHeader.numTriangles);

    // Texture Coordiantes
    Seek(F, MeshOffset + Meshes[I].MeshHeader.TexVectorStart);
    SetLength(Meshes[I].TexCoord, Meshes[I].MeshHeader.numVertexes);
    BlockRead(F, Meshes[I].TexCoord[0], sizeOf(TTexCoord)*Meshes[I].MeshHeader.numVertexes);

    // Vertices
    Seek(F, MeshOffset + Meshes[I].MeshHeader.VertexStart);
    SetLength(Meshes[I].Vertex, Meshes[I].MeshHeader.numVertexes * Meshes[I].MeshHeader.numMeshFrames);
    BlockRead(F, Meshes[I].Vertex[0], sizeOf(TVertex)*Meshes[I].MeshHeader.numVertexes * Meshes[I].MeshHeader.numMeshFrames);

    MeshOffset :=MeshOffset + Meshes[I].MeshHeader.MeshSize;
  end;

  CloseFile(F);

  // load animations (if any)
  s := ChangeFileExt(AFileName,'.ani');
  if FileExists(s) then
    LoadAnim(s);

  // set the start, end frame
  Header.numBoneFrames :=Header.numBoneFrames - 1;
  {startFrame := 0;
  endFrame := Header.numBoneFrames;}
  
  // check whether there is enought frames for all animations, it's a common error if not
  if (high(Anim)<0)or(Header.numBoneFrames<0) then
    raise Exception.Create('md3: at least one animation and one frame required in "'+Md3Name+'"');
  
  // check animation consistency
  if Anim[high(Anim)].FirstFrame+Anim[high(Anim)].NumFrames <> Header.numBoneFrames + 1 then
    raise Exception.Create(format('md3: inconsistent animations in "%s", High(Anim)=%d Anim[max].FirstFrame=%d Anim[max].NumFrames=%d Header.numBoneFrames=%d, unsatisfied condition: FirstFrame+NumFrames <> numBoneFrames + 1',
      [Md3Name,high(Anim),Anim[high(Anim)].FirstFrame,Anim[high(Anim)].NumFrames,Header.numBoneFrames]));
end;

procedure TMD3ModelStatic.LoadAnim(AFileName : string);
{*
Load animations from extra .ANI file
}
var 
  sfile,sline : TStringList;
  i : integer;
begin
  try
    sfile := TStringList.Create;
    sline := TStringList.Create;
    sline.Delimiter := ',';
    sfile.LoadFromFile(AFileName);
    // remove comments and empty lines
    for i := sfile.Count-1 downto 0 do
    begin
      // remove comments
      if pos('#',sfile[i]) > 0 then
        sfile[i] := copy(sfile[i],1,pos('#',sfile[i])-1);
      // remove empty lines
      if trim(sfile[i])='' then
        sfile.delete(i);
    end;
    // sfile.SaveToFile(AFileName+'.1');
    // load the animations
    //writeln('Loading ',sfile.Count,' animations');
    SetLength(Anim,sfile.Count);
    for i := 0 to sfile.Count-1 do
    with Anim[i] do
    begin
      sline.DelimitedText := sfile[i];
      Name := sline[0];
      FirstFrame := StrToInt(sline[1]);
      NumFrames := StrToInt(sline[2]);
      LoopingFrames := StrToInt(sline[3]);
      FPS := StrToInt(sline[4]);
      //writeln('animation: Name="',Name,'" FirstFrame=',FirstFrame,' NumFrames=',NumFrames,' LoopingFrames=',LoopingFrames,' FPS=',FPS);
    end;
  finally
    sfile.Free;
    sline.Free;
  end;
end;

procedure TMd3ModelDynamic.SetAnimation(AAnimationName : string);
{*
Find animation parameters in static and copy them to dynamic part
}
var i,c : integer;
begin
  Animation := AAnimationName;
  c := 0;
  for i := 0 to High(ModelStatic.Anim) do
    if ModelStatic.Anim[i].Name = AAnimationName then
    begin
      inc(c);
      Frame := ModelStatic.Anim[i].FirstFrame;
      StartFrame := Frame;
      NextFrame := Frame+1;
      if NextFrame>EndFrame then 
        NextFrame := EndFrame;
      EndFrame := Frame+ModelStatic.Anim[i].NumFrames-1;
      FPS := ModelStatic.Anim[i].FPS;
      //writeln('found ',AAnimationName);
      Debug.Msg(dlBegin,Format(ClassName+'.SetAnimation: Frame=%d StartFrame=%d NextFrame=%d EndFrame=%d FPS=%d',[Frame,StartFrame,NextFrame,EndFrame,FPS]));
    end;
  if c <> 1 then
    raise Exception.Create('Animation "'+AAnimationName+'" not found in model "'+ModelStatic.Md3Name+'" (count: '+inttostr(c)+')');
end;

procedure TMD3ModelStatic.LoadSkin(AImagePath, AFileName : string);
{*
Loads the skins for the Model from the .skin file
}
var 
  F : TextFile;
  I,texloadtimes : integer;
  S : string;
  MeshName, ImageName : string;
begin
  Debug.Msg(dlBegin,ClassName+'.LoadSkin ImagePath='+AImagePath+' FileName='+AFileName);
  if FileExists(AImagePath + AFileName) then
  begin
    AssignFile(F,AImagePath + AFileName);
    Reset(F);
    while EOF(F) = FALSE do
    begin
      Readln(F, S);
      if Length(S) > 1 then
      begin
        if Pos(',', S)+1 < Length(S) then   // there must be something after the comma
        begin
          MeshName :=Copy(S, 1, Pos(',', S)-1);
          if Copy(MeshName, 1, 4) <> 'tag_' then   // tags dont have skins
          begin
            ImageName :=Copy(S, Pos(',', S)+1, Length(S));     // get the full image and path name
            if pos('/',s) > 0 then // cut path only if there is any
            begin
              ImageName :=StrRScan(PChar(S), '/');               // get name from last / (i.e only AFileName)
              ImageName :=Copy(ImageName, 2, Length(ImageName)); // lose the starting /
            end;
            // if its a TGA or JPG or PNG, then Load the skin
            if (pos('.JPG', UpperCase(ImageName)) > 0) 
            or (pos('.PNG', UpperCase(ImageName)) > 0) 
            or (pos('.TGA', UpperCase(ImageName)) > 0) then
            begin
              // Find the right mesh item to assign the skin to
              texloadtimes := 0;
              for I :=0 to Header.numMeshes-1 do
              begin
                // check it the two names are the same
                //writeln('m[i]mhmn=',CharArrToStr(meshes[i].MeshHeader.Name),' meshnanme=',meshname);
                if UpperCase(CharArrToStr(meshes[i].MeshHeader.Name)) = Uppercase(meshname) then
                begin
                  //writeln('  loading texture from: '+AImagePath + ImageName);
                  meshes[i].Texture := TextureList.Load(AImagePath + ImageName);
                  // writeln('..texture=',integer(meshes[i].Texture));
                  //Texture := TexrureLists.LoadTexture(ImagePath + ImageName, meshes[i].Texture, FALSE);
                  meshes[i].SetTexture :=TRUE;
                  //writeln('SET_TEXTURE=true');
                  inc(texloadtimes);
                end;
              end;
              if texloadtimes = 0 then
                raise Exception.Create('Perhaps bug, texloadtimes=0 for "'+AImagePath+'" for skin "'+AFileName+'"');
            end;
          end;
        end;
      end;
    end;
    CloseFile(F);
  end;
end;

procedure TMD3ModelDynamic.UpdateFrame(time : glFLoat);
{*
Update animation using interpolation
}
begin
  Poll :=(Time - LastUpdate);
  //writeln('Time=',time:1:8,' Poll=',Poll:1:8);
  if FPS=0 then FPS:=1;
  if Poll > 1/FPS then
  begin
    Poll := Poll - 1/FPS;
    Frame := NextFrame;
    Inc(NextFrame);
    if NextFrame > EndFrame then
      NextFrame := StartFrame;
    LastUpdate := Time;
    //writeln('posun, f=',Frame,' sf=',StartFrame,' ef=',EndFrame,' nf=',NextFrame);
  end;
end;

destructor TMd3ModelStaticList.Destroy;
var i : integer;
begin
  for i := 0 to Count-1 do
    TMd3ModelStatic(Items[i]).Free;
  inherited;
end;

function TMd3ModelStaticList.Load(APath, AModel, ASkin : string) : TMd3ModelStatic;
{*
Load model
}
var i : integer;
begin
  // is it allready loaded?
  for i := 0 to Count-1 do
    if TMd3ModelStatic(Items[i]).Md3Name = APath+AModel+'.md3' then
    begin
      Debug.Msg(dlDetail,'model allready loaded');
      result := TMd3ModelStatic(Items[i]);
      exit;
    end;
  // model is not yet loaded, we must load it by ourself
  result := TMd3ModelStatic.Create;
  result.LoadModel(APath+AModel+'.md3');
  result.LoadSkin(APath,AModel+'_'+ASkin+'.skin');
  Add(result);
end;

procedure TMd3ModelDynamic.Base(ABaseTag : string);
{*
Satup base tag and base matrix for models that are not linked to anything (usually root model of hierarchical model)
}
begin
  BaseTag := ModelStatic.FindTag(ABaseTag);
  CalculateBaseMatrixFromTag(BaseMatrix,BaseTag);
end;  

procedure TMD3ModelDynamic.DrawModel;
{*
Draw single model
}
begin
  //writeln('TMd3ModelDynamic.DrawModel: ModelStatic.DrawModelInt(',Frame,',',NextFrame,',',Poll*FPS,');');
  ModelStatic.DrawModelInt(frame, nextframe, poll*FPS);
end;

procedure TMD3ModelDynamic.DrawLinked(AModel : TMd3ModelDynamic; ATime : glFloat);
{*
Draw linked models
}
var I : integer;
  pMdl : {P}TMD3ModelDynamic;
  m    : array[0..15] of glFloat;
  Rotation,Rotation2 : TRotationMatrix;
  Position,Position2 : TVector;
begin
  AModel.UpdateFrame(ATime);
  if AModel.BaseAngle <> 0 then
    glRotatef(AModel.BaseAngle,0,0,1);
  AModel.DrawModel;
  for I :=0 to AModel.ModelStatic.Header.numTags-1 do
  begin
    // writeln('i=',i,' high(AModel.links)=',high(AModel.Links));
    pMdl := AModel;
    if high(pMdl.Links)<0 then continue; //???
    if pMdl.Links[i] <> nil then
    begin
      Rotation :=AModel.ModelStatic.Tags[AModel.Frame * AModel.ModelStatic.Header.numTags + i].Rotation;
      Position :=AModel.ModelStatic.tags[AModel.frame * AModel.ModelStatic.Header.numTags + i].Position;
      Rotation2 :=AModel.ModelStatic.Tags[AModel.NextFrame * AModel.ModelStatic.Header.numTags + i].Rotation;
      Position2 :=AModel.ModelStatic.tags[AModel.NextFrame * AModel.ModelStatic.Header.numTags + i].Position;

      m[0] := Rotation[0, 0]*(1-poll*fps)+Rotation2[0,0]*poll*fps;
      m[1] := Rotation[0, 1]*(1-poll*fps)+Rotation2[0,1]*poll*fps;
      m[2] := Rotation[0, 2]*(1-poll*fps)+Rotation2[0,2]*poll*fps;
      m[3] := 0;
      m[4] := Rotation[1, 0]*(1-poll*fps)+Rotation2[1,0]*poll*fps;
      m[5] := Rotation[1, 1]*(1-poll*fps)+Rotation2[1,1]*poll*fps;
      m[6] := Rotation[1, 2]*(1-poll*fps)+Rotation2[1,2]*poll*fps;
      m[7] := 0;
      m[8] := Rotation[2, 0]*(1-poll*fps)+Rotation2[2,0]*poll*fps;
      m[9] := Rotation[2, 1]*(1-poll*fps)+Rotation2[2,1]*poll*fps;
      m[10]:= Rotation[2, 2]*(1-poll*fps)+Rotation2[2,2]*poll*fps;
      m[11]:= 0;
      m[12] := position[0]*(1-poll*fps)+position2[0]*poll*fps;
      m[13] := position[1]*(1-poll*fps)+position2[1]*poll*fps;
      m[14] := position[2]*(1-poll*fps)+position2[2]*poll*fps;
      m[15] := 1;
      // interpolated animation matrix
      glPushMatrix();
      glMultMatrixf(@m);
      // base matrix (this brings base tag to origin, used for hierarchical linking)
      glPushMatrix();
      glMultMatrixf(@pMdl.Links[i].BaseMatrix); // #1
      // draw all linked modules
      if longword(pMdl.Links[i]) > 0 then
        DrawLinked(pMdl.Links[i]{^},ATime);
      glPopMatrix();
      glPopMatrix();
    end;
  end;
end;

initialization

  InitNormals;
  Md3ModelStaticList := TMd3ModelStaticList.Create;

finalization

  Debug.Msg(dlInfo,'Releasing Md3ModelStaticList');
  Md3ModelStaticList.Free;

end.


